#include <iostream>
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>
#include <GL/glext.h>
#include <Cg/cgGL.h>
#define PI 3.14159265

struct TextureParams {
	char* name;
	GLenum target;
	GLenum internalFormat;
	GLenum format;
	GLenum type;
	char* shader;
} rect_nv_rgba_32,
rect_rgba_b,
twod_rgba_b;

const unsigned texSize = 512;
GLubyte *dataY;
unsigned readTex = 0;
unsigned writeTex = 0;
GLenum attachmentpoints[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
GLuint yTexID[2];
TextureParams texParams;
// Cg vars
CGcontext cgContext;
CGprofile fragmentProfile;
CGprogram fragmentProgram;
CGparameter yParam, xParam, alphaParam;



void checkGLErrors (const char *label) {
    GLenum errCode;
    const GLubyte *errStr;
    
    if ((errCode = glGetError()) != GL_NO_ERROR) {
	errStr = gluErrorString(errCode);
	printf("OpenGL ERROR: ");
	printf((char*)errStr);
	printf("(Label: ");
	printf(label);
	printf(")\n.");
    }
}



bool checkFramebufferStatus() {
    GLenum status;
    status = (GLenum) glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
    switch(status) {
        case GL_FRAMEBUFFER_COMPLETE_EXT:
            return true;
        case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
	    printf("Framebuffer incomplete, incomplete attachment\n");
            return false;
        case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
	    printf("Unsupported framebuffer format\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
	    printf("Framebuffer incomplete, missing attachment\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
	    printf("Framebuffer incomplete, attached images must have same dimensions\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
	    printf("Framebuffer incomplete, attached images must have same format\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
	    printf("Framebuffer incomplete, missing draw buffer\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
	    printf("Framebuffer incomplete, missing read buffer\n");
            return false;
    }
    return false;
}




void cgErrorCallback(void) {
    CGerror lastError = cgGetError();
    if(lastError) {
	printf(cgGetErrorString(lastError));
	printf(cgGetLastListing(cgContext));
	exit(-1);
    }
}



void swap(void) {
    unsigned tmp = readTex;
	
	readTex = writeTex;
	writeTex = tmp;
}



void display()
{
	glClear(GL_COLOR_BUFFER_BIT);
	glBindTexture(texParams.target, yTexID[readTex]);
	glPolygonMode(GL_FRONT, GL_FILL);

	glEnable(texParams.target);
	glBegin(GL_QUADS);

    glTexCoord2f(0.0, 0.0); 
    glVertex2f(0.0, 0.0);

	glTexCoord2f(texSize, 0.0); 
    //glTexCoord2f(1.0, 0.0); 
    glVertex2f(texSize, 0.0);

	glTexCoord2f(texSize, texSize);
    //glTexCoord2f(1.0, 1.0);
    glVertex2f(texSize, texSize);

	glTexCoord2f(0.0, texSize); 
    //glTexCoord2f(0.0, 1.0); 
    glVertex2f(0.0, texSize);

	glEnd();
	glDisable(texParams.target);

	glFlush();
}



void initGlut(int &argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitWindowSize(texSize, texSize);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("hello");
	glutDisplayFunc(display);
	glClearColor(0.0, 0.5, 0.0, 1.0);
}



void initGl()
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
	// gluOrtho2D: left, right, bottom, top
    gluOrtho2D(0.0, texSize, 0.0, texSize);
    //gluOrtho2D(0, 1, 0, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glViewport(0, 0, texSize, texSize);
}



void initCg()
{
    // set up Cg
    cgSetErrorCallback(cgErrorCallback);
    cgContext = cgCreateContext();
    fragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);
    cgGLSetOptimalOptions(fragmentProfile);
    // create fragment program
    fragmentProgram = cgCreateProgram (cgContext, CG_SOURCE, 
		texParams.shader, fragmentProfile, "myfunc", NULL);
    // load program
    cgGLLoadProgram (fragmentProgram);
    // and get parameter handles by name
    yParam = cgGetNamedParameter (fragmentProgram, "textureY");
}



void setupTextureParams()
{
	rect_rgba_b.target = GL_TEXTURE_RECTANGLE_ARB;
	rect_rgba_b.format = GL_RGBA;
	rect_rgba_b.internalFormat = GL_RGBA;
	rect_rgba_b.type = GL_UNSIGNED_BYTE;
    rect_rgba_b.shader = \
	"byte4 myfunc ("\
	"in float2 coords : TEXCOORD0, "\
	"uniform samplerRECT textureY ) : COLOR { "\
	"byte4 y = texRECT (textureY, coords); "\
	"return y.gbra; }";

    rect_nv_rgba_32.target = GL_TEXTURE_RECTANGLE_ARB;
    rect_nv_rgba_32.format = GL_RGBA;
    rect_nv_rgba_32.internalFormat = GL_FLOAT_RGBA32_NV;
	rect_nv_rgba_32.type = GL_FLOAT;
}



void createTexture(GLubyte *data, const GLuint &texID)
{
    // make active and bind
    glBindTexture(texParams.target,texID);
	
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    // define texture with floating point format
    glTexImage2D(texParams.target, 0, texParams.internalFormat, 
		texSize, texSize, 0, texParams.format, texParams.type, data);
	if (glGetError() != GL_NO_ERROR) {
		printf("glTexImage2D():\t\t\t [FAIL]\n");
		exit (-1);
    } else {
		printf("glTexImage2D():\t\t\t [PASS]\n");
    }

	// turn off filtering and wrap modes
    glTexParameteri(texParams.target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(texParams.target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(texParams.target, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(texParams.target, GL_TEXTURE_WRAP_T, GL_CLAMP);
}



int main(int argc, char **argv)
{
	unsigned N = texSize * texSize * 4;

	dataY = new GLubyte[N];
	for (unsigned y =  0; y < texSize; y++) {
		for (unsigned x = 0; x < texSize; x++) {
			dataY[4 * (x + texSize * y)]
			= dataY[4 * (x + texSize * y) + 1]	
			= dataY[4 * (x + texSize * y) + 2]
			= (GLubyte) ((x + y) % 8 == 0? 0x00 : 0xff);
			dataY[4 * (x + texSize * y) + 3]
			= (GLubyte) 0xff;
		}
	}

	initGlut(argc, argv);
	initGl();

	setupTextureParams();
	//texParams = twod_rgba_b;
	texParams = rect_rgba_b;
	glEnable(texParams.target);
	glGenTextures(2, yTexID);
	createTexture(dataY, yTexID[readTex]);
	createTexture(dataY, yTexID[writeTex]);

    // attach two textures to FBO
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, 
		attachmentpoints[writeTex], texParams.target, yTexID[writeTex], 0);
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, 
		attachmentpoints[readTex], texParams.target, yTexID[readTex], 0);
    // check if that worked
    if (!checkFramebufferStatus()) {
		printf("glFramebufferTexture2DEXT():\t [FAIL]\n");
		exit (-1);
    } else {
		printf("glFramebufferTexture2DEXT():\t [PASS]\n");
    }

	glutMainLoop();

	return 0;
}
